package com.lanmao.emc.strategy.impl;


import com.lanmao.emc.dao.UpgradeDaoFactory;
import com.lanmao.emc.entity.FeVersionPO;
import com.lanmao.emc.entity.ZipEntity;
import com.lanmao.emc.enums.DBType;
import com.lanmao.emc.strategy.UpgradeStrategy;
import com.lanmao.emc.utils.EsUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ResourceUtils;
import org.springframework.util.StringUtils;

import java.io.*;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.stream.Collectors;

import static com.lanmao.emc.constant.FieldConstant.INDEX_POST_TAG_SO;
import static com.lanmao.emc.constant.FieldConstant.TABLE_VERSION;
import static com.lanmao.emc.utils.UpgradeUtil.*;
import static java.nio.charset.StandardCharsets.UTF_8;

/**
 * 描述：
 *
 * @author lanmao
 * @since 2024年11月11日 20:42
 */
@Slf4j
@Service
public abstract class UpgradeStrategyImpl implements UpgradeStrategy {
    @Value("${es.upgrade.dir:indices}")
    private String fileDir;

    @Autowired
    protected UpgradeDaoFactory factory;

    @Override
    public void upgradeIndex() {
        log.info("current strategy is {}", getUpgradeType());
        List<Map.Entry<String, List<ZipEntity>>> listDir = getIndexDir();
        if (CollectionUtils.isEmpty(listDir)) {
            log.info("current version records is empty,upgrade end");
            return;
        }
        FeVersionPO feVersion = factory.getDao().findLastVersion();
        final String dbVersion = ObjectUtils.isEmpty(feVersion) ? "" : feVersion.getVersion();
        int dbLastId = ObjectUtils.isEmpty(feVersion) ? 0 : feVersion.getId();
        List<Map.Entry<String, List<ZipEntity>>> newVersionList = listDir.stream().filter(x -> isNewVersion(x.getKey(), dbVersion)).toList();
        if (CollectionUtils.isEmpty(newVersionList)) {
            String dirLastVersion = listDir.get(listDir.size() - 1).getKey();
            log.info("latest version:{},current version{}", dbVersion, dirLastVersion);
            handleFailedIndex();
            return;
        }
        for (Map.Entry<String, List<ZipEntity>> dir : newVersionList) {
            int count = 0;
            String versionDesc = "";
            log.info("current upgrade version is {}", dir.getKey());
            for (ZipEntity zipEntity : dir.getValue()) {
                String indexName = zipEntity.getIndexName();
                String newIndexName = getNewIndexName(indexName + "_" + dir.getKey());
                versionDesc = zipEntity.getDesc();
                try {
                    count += upgradeIndex(indexName, zipEntity.getJson(), newIndexName);
                } catch (Exception e) {
                    log.error("upgrade index {} error,", indexName, e);
                }
            }
            saveVersion(count > 0, ++dbLastId, dir.getKey(), versionDesc);
            doFinally();
            log.info("{} index upgrade success,version:{}", count, dir.getKey());
        }
    }

    /**
     * 升级结束的操作，目前只有多版本策略有具体的实现
     */
    void doFinally() {
        log.info("success");
    }

    /**
     * 保存当前升级版本信息
     *
     * @param hasRecord   是否有升级成功的记录
     * @param dbLastId    版本表最后一个id
     * @param version     升级的版本
     * @param versionDesc 版本描述
     */
    private void saveVersion(boolean hasRecord, int dbLastId, String version, String versionDesc) {
        if (hasRecord) {
            FeVersionPO feVersion = new FeVersionPO(version, versionDesc, LocalDateTime.now(), dbLastId, version);
            if (StringUtils.hasText(versionDesc)) {
                feVersion.setRemark(version + "_" + versionDesc);
            }
            factory.getDao().saveVersion(feVersion);
        }
    }

    /**
     * 具体的升级细节由各自策略实现
     *
     * @param indexName    索引名
     * @param json         索引mapping信息
     * @param newIndexName 新索引名
     * @return 是否成功，1是，0否
     */
    abstract int upgradeIndex(String indexName, String json, String newIndexName);

    String getNewIndexName(String index) {
        return index.split("_")[0] + INDEX_POST_TAG_SO;
    }

    /**
     * 重新拉起上次失败的索引
     */
    void handleFailedIndex() {
    }

    /**
     * 判断是否新版本
     *
     * @param v1 版本1
     * @param v2 版本2
     * @return 是否新版，大于v2就是新版
     */
    private boolean isNewVersion(String v1, String v2) {
        return !StringUtils.hasText(v2) || compareVersion(v1, v2) > 0;
    }

    /**
     * 将资源目录的索引文件读出来
     *
     * @return 索引信息
     */
    private List<Map.Entry<String, List<ZipEntity>>> getIndexDir() {
        URL url = this.getClass().getClassLoader().getResource("");
        assert url != null;
        System.out.println(url);
        List<ZipEntity> list = new ArrayList<>();
        if (url.getProtocol().startsWith("jar")) {
            remoteDirLoad(url, list);
        } else {
            localDirLoad(list);
        }
        Map<String, List<ZipEntity>> dirMap = list.stream().collect(Collectors.groupingBy(ZipEntity::getVersion));
        return dirMap.entrySet().stream().sorted((k1, k2) -> compareVersion(k1.getKey(), k2.getKey())).toList();
    }

    /**
     * 版本比较
     *
     * @param v1 版本1
     * @param v2 版本2
     * @return v1大返回1，v2大返回-1，一样大返回0
     */
    private int compareVersion(String v1, String v2) {
        String[] v1List = v1.split("\\.");
        String[] v2List = v2.split("\\.");
        for (int i = 0; i < v2List.length || i < v1List.length; i++) {
            int v11 = 0, v22 = 0;
            if (i < v1List.length) {
                v11 = Integer.parseInt(v1List[i]);
            }
            if (i < v2List.length) {
                v22 = Integer.parseInt(v2List[i]);
            }
            if (v11 > v22) {
                return 1;
            }
            if (v11 < v22) {
                return -1;
            }
        }
        return 0;
    }

    /**
     * 本地资源解析
     *
     * @param list 解析结果集
     */
    private void localDirLoad(List<ZipEntity> list) {
        Resource resources = new PathMatchingResourcePatternResolver().getResource(ResourceUtils.CLASSPATH_URL_PREFIX + fileDir);
        try {
            List<File> fileList = Arrays.stream(Objects.requireNonNull(resources.getFile().listFiles())).filter(File::isDirectory).toList();
            for (File dir : fileList) {
                String dirName = dir.getName();
                String[] dirArray = dirName.split("_", 2);
                String desc = "";
                String version = dirArray[0];
                if (dirArray.length == 2) {
                    desc = dirArray[1];
                }
                for (File file : Objects.requireNonNull(dir.listFiles())) {
                    String indexName = Objects.requireNonNull(file.getName()).split("\\.")[0];
                    String json = FileUtils.readFileToString(file, StandardCharsets.UTF_8);
                    list.add(new ZipEntity(version, desc, indexName, json));
                }
            }
        } catch (Exception e) {
            log.error("read indices dir error,{}", e.getMessage());
        }
    }

    /**
     * 异常处理，回滚，重试
     *
     * @param oldIndexName 旧索引名
     * @param alias        索引别名
     * @param newIndexName 新索引名
     * @param e            异常
     * @return 是否成功
     */
    static int handleException(String oldIndexName, String alias, String newIndexName, Exception e) {
        log.error("upgrade index {} error,detail:", alias, e);
        log.error("try to rollback or retry upgrade");
        try {
            if (EsUtils.existsIndex(oldIndexName)) {
                List<String> indexAlias = EsUtils.getIndexAlias(newIndexName);
                indexAlias.add(alias);
                EsUtils.createAlias(oldIndexName, indexAlias.toArray(new String[0]));
                EsUtils.delIndex(newIndexName);
                log.error("index {} rollback success!", alias);
                return 0;
            } else {
                EsUtils.createAlias(newIndexName, alias);
                log.error("index {} retry upgrade success! ", alias);
                return 1;
            }
        } catch (Exception ex) {
            log.error("rollback or retry upgrade {} failed", newIndexName);
            return 0;
        }
    }

    /**
     * 远程jar协议解析
     *
     * @param url  resource目录地址
     * @param list 解析结果
     */
    private void remoteDirLoad(URL url, List<ZipEntity> list) {
        String subString = url.getPath().substring(5);
        String path = subString.split("!", 2)[0];
        String postPath = "BOOT-INF/classes/" + fileDir;

        try (JarFile jarFile = new JarFile(path)) {
            Enumeration<JarEntry> entries = jarFile.entries();
            while (entries.hasMoreElements()) {
                JarEntry jarEntry = entries.nextElement();
                String relPath = jarEntry.getName();
                if (relPath.startsWith(postPath) && (!jarEntry.isDirectory())) {
                    ZipEntity entity;
                    try (InputStream inputStream = jarFile.getInputStream(jarEntry)) {
                        String[] pathArray = relPath.split("/");
                        String dirName = pathArray[pathArray.length - 2];
                        String[] dirArray = dirName.split("_", 2);
                        String desc = "";
                        String version = dirArray[0];
                        if (dirArray.length == 2) {
                            desc = dirArray[1];
                        }
                        String indexJsonName = pathArray[pathArray.length - 1];
                        String indexName = Objects.requireNonNull(indexJsonName).split("\\.")[0];
                        String json = IOUtils.toString(inputStream, UTF_8);
                        entity = new ZipEntity(version, desc, indexName, json);
                        list.add(entity);
                    }
                }
            }

        } catch (IOException e) {
            throw new RuntimeException(e);
        }

    }

    @Override
    public void rollback(String index, String version) {
        throw new RuntimeException("当前策略不支持回滚");
    }

    @Override
    public void initTable() {
        if (!factory.getDao().allTableExist(TABLE_VERSION)) {
            if (factory.getDao().getDbType() == DBType.ES) {
                Resource resource = new PathMatchingResourcePatternResolver()
                        .getResource(ResourceUtils.CLASSPATH_URL_PREFIX + factory.getDao().getDbType().getType() + "/" + "version.json");
                try (InputStream inputStream = resource.getInputStream()) {
                    String json = IOUtils.toString(inputStream, UTF_8);
                    EsUtils.addIndex(getEsTable(TABLE_VERSION), json);
                } catch (Exception e) {
                    log.error("init es table error,", e);
                }
                return;
            }
            Resource resource = new PathMatchingResourcePatternResolver()
                    .getResource(ResourceUtils.CLASSPATH_URL_PREFIX + factory.getDao().getDbType().getType() + "/" + "sample.sql");
            List<String> sqlList = readSqlFile(resource);
            factory.getDao().batchupdate(sqlList);
        }
    }


    Resource[] getUpgradeDir(String dirName) {
        try {
            return new PathMatchingResourcePatternResolver().getResources(ResourceUtils.CLASSPATH_URL_PREFIX + fileDir + "/" + dirName + "/*");

        } catch (IOException e) {
            log.error("read upgrade dir error,", e);
            return new Resource[0];
        }
    }

    /**
     * 读取sql文件
     *
     * @param resource 资源文件
     * @return sql列表
     */
    List<String> readSqlFile(Resource resource) {
        List<String> sqlList = new ArrayList<>();
        StringBuilder sb = new StringBuilder();
        try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(resource.getInputStream()))) {
            String line;
            while ((line = bufferedReader.readLine()) != null) {
                if (line.trim().isEmpty() || line.startsWith("---")) {
                    continue;
                }
                sb.append(line).append(" ");
                if (line.trim().endsWith(";")) {
                    sqlList.add(sb.toString().trim());
                    sb = new StringBuilder();
                }
            }
            if (sb.length() > 0) {
                sqlList.add(sb.toString().trim());
            }
        } catch (IOException e) {
            log.error(e.getMessage());
        }
        return sqlList;
    }

}